package com.weimob.mcloud.wechatwork.customer.aop;

import com.alibaba.fastjson.JSON;
import com.ctrip.framework.apollo.spring.annotation.ApolloJsonValue;
import com.dianping.cat.Cat;
import com.dianping.cat.message.Event;
import com.weimob.mcloud.wechatwork.customer.service.user.exception.BaseErrorCode;
import com.weimob.mcloud.wechatwork.customer.service.user.exception.BizException;
import com.weimob.soa.common.exception.SoaException;
import com.weimob.soa.common.response.SoaResponse;
import com.weimob.soa.common.utils.SoaUtil;
import com.weimob.zipkin.ZipkinContext;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.time.StopWatch;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.hibernate.validator.internal.engine.path.PathImpl;
import org.springframework.context.annotation.Configuration;

import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.*;

@Slf4j
@Aspect
@Configuration
public class DubboLogAspect {

    /**
     * 路径拼接符
     */
    private static final String PATH_JOINER = " && ";

    /**
     * 拦截的路径：
     * 1.拦截export包下的所有类的所有方法；
     */
    private static final String INCLUDE_PATHS
            = "execution(* com.weimob.mcloud.wechatwork.customer.export..*.*(..))";

    private static final String INCLOUD_SERVICE_PATH
            = "execution(* com.weimob.mcloud.wechatwork.customer.service..*.*(..))";

//    private static final String INCLOUD_FACADE_PATH
//            = "execution(* com.weimob.mcloud.wechatwork.customer.facade.impl..*.*(..))";

    /**
     * 排除的路径：
     * 1.排除切面所在的包；
     * 2.排除config包下所有类的方法拦截；
     * 3.排除容器心跳检测的方法拦截。
     *
     * <p>
     * 我十分担心后续又有人增加拦截范围时把config包也无意间加进去了，
     * 这会导致我要打印我自己的循环栈溢出，算是Jackson的一个大坑了。
     * TODO：把fastjson全部换成jackson实现
     */
    private static final String EXCLUDE_PATHS
            = "!execution(* com.weimob.mcloud.wechatwork.customer.aop..*.*(..))" + PATH_JOINER +
            "!execution(* com.weimob.mcloud.wechatwork.customer.config..*.*(..))";

    @ApolloJsonValue("${filter.method.list:[]}")
    private List<String> filterMethodLists;

    @Around(INCLUDE_PATHS + PATH_JOINER + INCLOUD_SERVICE_PATH + PATH_JOINER  + EXCLUDE_PATHS)
    public Object aroundSoaService(ProceedingJoinPoint pjp) {
        String globalTicket = ZipkinContext.getContext().getGlobalTicket();
        log.info(String.format("{\"globalTicket\":%s,\"class\":%s,\"method\":%s,\"params\":%s}",
                globalTicket, pjp.getTarget().getClass().getSimpleName(),
                pjp.getSignature().getName(), JSON.toJSONString(pjp.getArgs())));
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Object proceed;
        try {
            proceed = pjp.proceed();
            if (proceed != null) {
                if (proceed instanceof SoaResponse) {
                    ((SoaResponse) proceed).setGlobalTicket(globalTicket);
                    String returnCode = ((SoaResponse) proceed).getReturnCode();
                    String returnMsg = ((SoaResponse) proceed).getReturnMsg();
                    Cat.logEvent("ReturnCode", String.format("code_%s", returnCode), Event.SUCCESS, returnMsg);
                }
            }
            stopWatch.split();
            String output = filterMethodLists.contains(pjp.getSignature().getName().toLowerCase()) ?
                    "":",\"output\":" + JSON.toJSONString(proceed);
            log.info("{\"globalTicket\":" + globalTicket +
                    ",\"clazz\":" + pjp.getTarget().getClass().getName() +
                    ",\"method\":" + pjp.getSignature().getName() +
                    ",\"input\":" + JSON.toJSONString(pjp.getArgs()) +
                    output +
                    ",\"cost\":" + (stopWatch.getSplitTime()) +
                    "}");
            return proceed;
        } catch (BizException e) {
            log.info("业务异常:BizException,globalTicket[{}], code[{}], msg[{}]", globalTicket, e.getErrcode(), e.getErrmsg());
            Cat.logEvent("ReturnCode", String.format("code_%s", e.getErrcode()), Event.SUCCESS, e.getErrmsg());
            return SoaUtil.error(String.valueOf(e.getErrcode()), e.getErrmsg(), null);
        } catch (SoaException e) {
            log.info("业务异常:SoaException,globalTicket[{}], code[{}], msg[{}]", globalTicket, e.getErrcode(), e.getErrmsg());
            Cat.logEvent("ReturnCode", String.format("code_%s", e.getErrcode()), Event.SUCCESS, e.getErrmsg());
            return SoaUtil.error(String.valueOf(e.getErrcode()), e.getErrmsg(), null);
        }catch (IllegalArgumentException e) {
            log.error("参数异常:[{}],globalTicket:[{}]", e.getMessage(), globalTicket);
            return SoaUtil.error(new SoaException(String.valueOf(BaseErrorCode.PARMA_EXCPETION.getCode()), BaseErrorCode.PARMA_EXCPETION.getMsg()), null);
        } catch (UnsupportedOperationException e) {
            log.error("非法操作:[{}],globalTicket:[{}]", e.getMessage(), globalTicket);
            return SoaUtil.error(new SoaException(String.valueOf(BaseErrorCode.OPERATION_NOT_ALLOWED.getCode()), BaseErrorCode.OPERATION_NOT_ALLOWED.getMsg()), null);
        } catch (ConstraintViolationException e) {
            List<Map<String, Object>> fields = new ArrayList();
            Iterator iterator = e.getConstraintViolations().iterator();
            while (iterator.hasNext()) {
                ConstraintViolation<?> cv = (ConstraintViolation) iterator.next();
                String fieldName = ((PathImpl) cv.getPropertyPath()).getLeafNode().asString();
                String message = cv.getMessage();
                Map<String, Object> field = new HashMap(2);
                field.put("field", fieldName);
                field.put("message", message);
                fields.add(field);
            }
            log.error("参数异常:[{}],globalTicket:[{}]", fields, globalTicket);
            return SoaUtil.error(new SoaException(String.valueOf(BaseErrorCode.PARMA_EXCPETION.getCode()), BaseErrorCode.PARMA_EXCPETION.getMsg()), fields);
        } catch (Throwable throwable) {
            stopWatch.stop();
            String errorMsg = String.format("{\"globalTicket\":%s,\"clazz\":%s,\"method\":%s,\"cost\":%s,\"input\":%s}", globalTicket, pjp.getTarget().getClass().getSimpleName(),
                    pjp.getSignature().getName(), stopWatch.getTime(), JSON.toJSONString(pjp.getArgs()));
            log.error(errorMsg, throwable);
            Cat.logEvent("ReturnCode", "code_9999", Event.FAIL, globalTicket + ":" + throwable.getMessage());
            SoaResponse soaResponse = new SoaResponse();
            soaResponse.setReturnCode(String.valueOf(BaseErrorCode.FAILED.getCode()));
            soaResponse.setReturnMsg(BaseErrorCode.FAILED.getMsg());
            return soaResponse;
        }
    }

}
